package com.leyou.search.service.impl;

import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.BrandDTO;
import com.leyou.item.dto.CategoryDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.item.dto.SpuDTO;
import com.leyou.search.mapper.GoodsRepository;
import com.leyou.search.pojo.Goods;
import com.leyou.search.service.SearchService;
import com.leyou.search.utils.ElasticService;
import com.leyou.search.utils.SearchRequest;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Created by IntelliJ IDEA.
 *
 * @author: wangzibeng
 * Date: 2019/5/6 0006
 * Time: 22:38
 */
@Service
public class SearchServiceImpl implements SearchService {
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate elaTemplate;

    @Autowired
    private ItemClient itemClient;
    @Autowired
    private ElasticService elasticService;

    @Override
    public PageResult<Goods> queryGoodsByPage(SearchRequest searchRequest) {
        return getGoodsPage(searchRequest, new NativeSearchQueryBuilder());
    }

    @Override
    public PageResult<Goods> queryGoodsByPageAndPrice(SearchRequest searchRequest) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //排序查询
        queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
        return getGoodsPage(searchRequest, queryBuilder);
    }

    @Override
    public PageResult<Goods> queryGoodsByPageAndStock(SearchRequest searchRequest) {
        return getGoodsPage(searchRequest, new NativeSearchQueryBuilder());
    }

    @Override
    public PageResult<Goods> queryGoodsByPageAndUpdate(SearchRequest searchRequest) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC));
        return getGoodsPage(searchRequest, queryBuilder);
    }

    @Override
    public Map<String, List<?>> queryFilter(SearchRequest searchRequest) {
        // 1 创建过滤项集合
        Map<String, List<?>> map = new LinkedHashMap<>();
        // 2 查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.1 获取查询条件
        QueryBuilder basicQuery = buildBasicQuery(searchRequest);
        queryBuilder.withQuery(basicQuery);
        // 2.2 减少查询结果(只需要聚合的结果)
        // 2.2.1 每页显示一个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 2.2.2 空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        // 3 聚合条件
        // 3.1 聚合名称
        String categoryAgg = "categoryAgg";
        // 3.2 分类聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        String brandAgg = "brandAgg";
        // 3.3 品牌聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        // 4 查询并解析结果
        AggregatedPage<Goods> goods = elaTemplate.queryForPage(queryBuilder.build(), Goods.class);
        // 4.1 获取所有的聚合
        Aggregations aggregations = goods.getAggregations();
        // 4.2 获取分类的聚合 并存入
        LongTerms categoryTerms = aggregations.get(categoryAgg);
        List<Long> ids = handleCategoryAgg(categoryTerms, map);
        //4.3 获取品牌的聚合 并存入
        LongTerms brandTerms = aggregations.get(brandAgg);
        handleBrandAgg(brandTerms, map);
        //根据分类ids查询规格参数
        if (ids != null && ids.size() == 1) {
            handleSpecParam(ids.get(0), searchRequest, map);
        }
        return map;
    }

    /**
     * 处理品牌的聚合
     *
     * @param terms     集合的字段
     * @param mapFilter 过滤项集合
     */
    private void handleBrandAgg(LongTerms terms, Map<String, List<?>> mapFilter) {
        //解析bucket 得到id集合
        List<Long> ids = terms.getBuckets().stream().map(LongTerms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //根据id查询品牌
        List<BrandDTO> brandDTOList = itemClient.queryBrandsByIds(ids);
        //存入
        mapFilter.put("品牌", brandDTOList);
    }

    /**
     * 处理分类的聚合
     *
     * @param terms     聚合的字段
     * @param mapFilter 过滤项集合
     * @return
     */
    private List<Long> handleCategoryAgg(LongTerms terms, Map<String, List<?>> mapFilter) {
        //解析bucket 得到id集合
        List<Long> ids = terms.getBuckets().stream().map(LongTerms.Bucket::getKeyAsNumber).map(Number::longValue).collect(Collectors.toList());
        //根据id查询品牌
        List<CategoryDTO> categoryDTOList = itemClient.queryCategoryByIds(ids);
        //存入
        mapFilter.put("分类", categoryDTOList);
        return ids;
    }

    /**
     * 处理规格参数的聚合
     *
     * @param id            分类id
     * @param searchRequest 查询条件
     * @param mapFilter     过滤项集合
     */
    private void handleSpecParam(Long id, SearchRequest searchRequest, Map<String, List<?>> mapFilter) {
        // 1 查询规格参数下需要过滤的规格的参数
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParams(null, id, true);
        // 2 查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.1 获取查询条件
        QueryBuilder basicQuery = buildBasicQuery(searchRequest);
        queryBuilder.withQuery(basicQuery);
        // 2.2 只需要集合的结果 （es 中size不能为0）
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 2.3 source为空
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());
        // 3 聚合条件
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            // 3.1 获取参数的name作为聚合的名称
            String name = specParamDTO.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }
        // 查询并获取结果
        AggregatedPage<Goods> goods = elaTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = goods.getAggregations();
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            String name = specParamDTO.getName();
            StringTerms terms = aggregations.get(name);
            List<String> collect = terms.getBuckets()
                    .stream()
                    .map(StringTerms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toList());
            //存入过滤集合
            mapFilter.put(name, collect);
        }
    }

    /**
     * 封装构建查询条件创建
     * 修改添加过滤条件
     *
     * @param request 查询请求
     * @return 查询器
     */
    private QueryBuilder buildBasicQuery(SearchRequest request) {
        // 构建基本bool查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 构建基本的bool查询
        boolQuery.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //判断过滤项集合不为空在添加过滤字段
        if (!CollectionUtils.isEmpty(request.getFilterMap())) {
            // 构建过滤查询
            for (Map.Entry<String, String> entry : request.getFilterMap().entrySet()) {
                String key = entry.getKey();
                if ("分类".equals(key)) {
                    key = "categoryId";
                } else if ("品牌".equals(key)) {
                    key = "brandId";
                } else {
                    key = "specs." + key;
                }
                //key 和 value 作为过滤条件
                boolQuery.filter(QueryBuilders.termQuery(key, entry.getValue()));
            }
        }
        return boolQuery;
    }

    private PageResult<Goods> getGoodsPage(SearchRequest searchRequest, NativeSearchQueryBuilder queryBuilder) {
        //1 获取请求参数健壮性判断
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        //2 构建查询条件
        //2.1 过滤字段 只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));
        //2.2 关键字match匹配查询条件
        queryBuilder.withQuery(buildBasicQuery(searchRequest));
        //2.3 分页
        int page = searchRequest.getPage();
        int size = searchRequest.getSize();
        //从0开始 所以-1
        queryBuilder.withPageable(PageRequest.of(page - 1, size));
        //查询获取结果
        Page<Goods> pageResult = goodsRepository.search(queryBuilder.build());
        if (pageResult == null) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        return new PageResult<>(pageResult.getTotalElements(), pageResult.getTotalPages(), pageResult.getContent());
    }

    @Override
    public void createIndex(Long spuId) {
        //先从数据库中查出来，在添加到数据库
        SpuDTO spuDTO = itemClient.querySpuById(spuId);
        if (spuDTO == null) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        Goods save = goodsRepository.save(elasticService.buildGoods(spuDTO));
        System.out.println("新增索引"+save);
    }

    @Override
    public void deleteIndex(Long spuId) {
        goodsRepository.deleteById(spuId);
        System.out.println("删除索引");
    }
}
